using System;
using Moq;
using NUnit.Framework;
using Tycho.Grammar;
using Tycho.Parser.Peg;

namespace Tycho.UnitTests.Grammar {
    [TestFixture]
    public class SequenceRuleInfixInformationBuilderSpecification {
        [Test]
        public void IfFirstAndLastRulesHaveProductionsThenIsInfix () {
            var production = new Mock<IProduction> ().Object;

            var first = CreateRuleWithProduction (production);
            var last = CreateRuleWithProduction (production);

            var infix = BuildInfixInformation (first, CreateRule (), last);

            Assert.That (infix.IsInfixWith (production));
            Assert.That (infix.First, Is.SameAs (first));
            Assert.That (infix.Last, Is.SameAs (last));
        }

        [Test]
        public void IfFirstAndLastRulesHaveProductionsThenIsInfixEvenAfterProductionsHaveChanged () {
            var production = new Mock<IProduction> ().Object;

            var first = CreateRuleWithProduction (production);
            var last = CreateRuleWithProduction (production);
            var builder = PrepareBuilderWith (first, CreateRule (), last);

            first.Production = new Mock<IProduction> ().Object;

            var infix = builder.CreateInfixInformation ();

            Assert.That (infix.IsInfixWith (production));
            Assert.That (infix.First, Is.SameAs (first));
            Assert.That (infix.Last, Is.SameAs (last));
        }

        [Test]
        public void IfFirstAndLastRulesHaveProductionsThatAreNotTheSameThenIsNotInfix () {
            var production = new Mock<IProduction> ().Object;
            var otherProduction = new Mock<IProduction> ().Object;

            var first = CreateRuleWithProduction (production);
            var last = CreateRuleWithProduction (otherProduction);

            var infix = BuildInfixInformation (first, CreateRule (), last);

            Assert.False (infix.IsInfixWith (production));
        }

        [Test]
        public void IfOnlyLastRuleHasProductionThenIsNotInfix () {
            var production = new Mock<IProduction> ().Object;
            var last = CreateRuleWithProduction (production);

            var infix = BuildInfixInformation (CreateRule (), last);

            Assert.False (infix.IsInfixWith (production));
        }

        [Test]
        public void IfOnlyFirstRuleHasProductionThenIsNotInfix () {
            var production = new Mock<IProduction> ().Object;
            IRule first = CreateRuleWithProduction (production);

            IInfixInformation infix = BuildInfixInformation (first, CreateRule ());

            Assert.False (infix.IsInfixWith (production));
        }

        private static IInfixInformation BuildInfixInformation (params IRule [] rules) {
            ISequenceRuleInfixInformationBuilder builder = PrepareBuilderWith (rules);

            return builder.CreateInfixInformation ();
        }

        private static ISequenceRuleInfixInformationBuilder PrepareBuilderWith (params IRule[] rules) {
            ISequenceRuleInfixInformationBuilder builder = new SequenceRuleInfixInformationBuilder ();

            foreach (var rule in rules) {
                builder.Add (rule);
            }
            return builder;
        }

        private IRule CreateRule () {
            return new Mock<IRule> ().Object;
        }

        private static IRule CreateRuleWithProduction (IProduction production) {
            return new RuleWithProduction (production);
        }

        private class RuleWithProduction : IRule {
            public RuleWithProduction (IProduction production) {
                Production = production;
            }

            public bool HasProduction {
                get { return true; }
            }

            public IProduction Production { get; set; }

            public RuleParseResult Parse (char[] source, int index, ParseContext context, string sourceString, ParseEnvironment environment) {
                throw new NotImplementedException ();
            }

            public Yield Parse(char[] source, int index, ParseContext context, string sourceString, ParseEnvironment environment, Func<RuleParseResult, Yield> continuation)
            {
                throw new NotImplementedException();
            }

            public bool IsInfix {
                get { throw new NotImplementedException (); }
            }

            public IInfixInformation InfixInformation {
                get { throw new NotImplementedException (); }
            }
        }
    }
}